/*
 * @(#)ScriptDirective.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.core.result.template.directive;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

import com.nhncorp.lucy.common.util.ResourceLoader;

import com.nhncorp.usf.core.config.runtime.ScriptEngineGlobals;
import com.nhncorp.usf.core.servlet.ServletContextHolder;
import com.nhncorp.usf.core.util.ScriptUtil;

import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;

/**
 * @author Web Platform Development Team
 */
public class ScriptDirective extends AbstractTemplateDirectiveModel {
    @SuppressWarnings("unchecked")
	private Map usfDataModel;
    private ResourceLoader resourceLoader = new ResourceLoader();

    /**
     * Instantiates a new script directive.
     *
     * @param dataModel the data model
     */
    @SuppressWarnings("unchecked")
	public ScriptDirective(Map dataModel) {
        usfDataModel = dataModel;
    }

    /**
     * @param env      Environment
     * @param params   Map
     * @param loopVars TemplateModel
     * @param body     TemplateDirectiveBody
     * @throws TemplateException the TemplateException
     * @throws IOException       the IOException
     * @see freemarker.template.TemplateDirectiveModel#execute(freemarker.core.Environment, java.util.Map, freemarker.template.TemplateModel[], freemarker.template.TemplateDirectiveBody)
     */
    @SuppressWarnings("unchecked")
	public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
        String scriptfile = getAsString(params.get("scriptfile")); //scriptfile program text
        String scriptType = getEngineType(params.get("type"));
        String method = getAsString(params.get("method"));
        String assign = getAsString(params.get("returnid"));
        Object param = convertInputParamsType(params.get("param"));

        Object value = null;
        ScriptEngine scriptEngine = null;
        Map<String, ScriptEngine> engineMap = ScriptEngineGlobals.getScriptEngineForWebModule(ServletContextHolder.getContextRealPath());

        if (engineMap == null) {
            throw new TemplateException("fail to get script engine [" + scriptType + "]", env);
        }

        scriptEngine = engineMap.get(scriptType);

        if (scriptEngine == null) {
            throw new TemplateException("fail to get script engine [" + scriptType + "]", env);
        }

        if (method == null) {
            throw new TemplateException("script function name do not exist!", env);
        } else {
            if (scriptfile == null) {
                value = getScriptResult(scriptEngine, method, param, env);
            } else {
                value = scriptFileEval(scriptfile, scriptEngine, param, method, env);
            }

            log.debug("script return value : " + value + ", param :  " + param);

            if (assign != null && value != null) {
                Object object = env.getObjectWrapper().wrap(value);
                env.setVariable(assign, (TemplateModel)object);
            }
            if (body != null) {
                body.render(env.getOut());
            }
        }
    }

    /**
     * Script file eval.
     *
     * @param scriptfile   the scriptfile
     * @param scriptEngine the script engine
     * @param param        the param
     * @param method       the method
     * @param env          the env
     * @return the object
     * @throws IOException       Signals that an I/O exception has occurred.
     * @throws TemplateException the template exception
     */
    private Object scriptFileEval(String scriptfile, ScriptEngine scriptEngine, Object param, String method, Environment env) throws IOException, TemplateException {
        URL url = resourceLoader.getResource(scriptfile);
        log.debug("loading scriptfile : " + scriptfile + "(" + url + ") script engine :  " + scriptEngine);

        if (url != null) {
            File file = new File(url.getFile());
            log.debug("script file full path : " + file);
            Bindings bindings = scriptEngine.createBindings();

            for (Object object : usfDataModel.keySet()) {
                String tkey = (String)object;
                Object tvalue = usfDataModel.get(tkey);
                log.debug("script function parameter key = " + tkey + ", value = " + tvalue);
                bindings.put(tkey, tvalue);
            }

            if (param != null) {
                bindings.put("param", param);
            }

            FileReader fr = new FileReader(file);
            try {
                scriptEngine.eval(fr, bindings);
                return scriptEngine.eval(method.concat("()"), bindings);
            } catch (ScriptException e) {
                e.printStackTrace();
                throw new TemplateException("fail to execute script file : " + file.getName(), e, env);
            } finally {
                if (fr != null) {
                    fr.close();
                }
            }
        }

        return null;
    }

    /**
     * Gets the script result.
     *
     * @param scriptEngine the script engine
     * @param functionName the function name
     * @param inputParam   the input param
     * @param env          the env
     * @return the script result
     * @throws TemplateException the template exception
     */
    private Object getScriptResult(ScriptEngine scriptEngine, String functionName, Object inputParam, Environment env) throws TemplateException {
        try {
            return ScriptUtil.invokeScript(scriptEngine, functionName, inputParam);
        } catch (ScriptException e) {
            e.printStackTrace();
            throw new TemplateException("fail to execute script function : " + functionName, e, env);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            throw new TemplateException("fail to find script function : " + functionName, e, env);
        }
    }

    /**
     * Convert input params type.
     *
     * @param param the param
     * @return the object
     * @throws TemplateModelException the template model exception
     */
    private Object convertInputParamsType(Object param) throws TemplateModelException {
        Object inputParams;

        if (param == null) {
            inputParams = usfDataModel;
        } else {
            inputParams = getAsList(param);

            if (inputParams == null) {
                inputParams = getAsString(param);
            }
        }

        return inputParams;
    }
}
